﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Cubicle.Excel.Shared;
using ExcelDna.Integration;

namespace Cubicle.Excel.Functions
{
    public static class GeneralFunctions
    {
        // Nlog
        private static NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();

        #region Commands

        //[ExcelCommand(MenuName = "Cubicle", MenuText = "Bind")]
        //public static void Bind()
        //{
        //    var proxyCortex = CubicleAddIn.Engines.Get(_scriptTarget);
        //    CubicleAddIn.Binder.BindScript(_script, proxyCortex);
        //}

        //[ExcelCommand(MenuName = "Cubicle", MenuText = "Execute Script")]
        //public static void RunScript()
        //{
        //    var Engine = CubicleAddIn.Engines.Get(_scriptTarget);

        //    var script = Base.Shared.Helpers.ReadBytesFromFile(_script);
        //    var result = Engine.RunScript(script);
        //    if (result)
        //        _logger.Info("Successfully executed script. Path = {0}", _script);
        //    else
        //        _logger.Error("Failed to execute script. Path = {0}", _script);
        //}

        //[ExcelCommand(MenuName = "Cubicle", MenuText = "Interface")]
        //public static void OpenBrowser()
        //{
        //    var address = Cubicle.Base.Shared.Helpers.ConfigFile().AppSettings.Settings["uiAddress"].Value;
        //    System.Diagnostics.Process.Start(address);
        //}

        #endregion

        #region Callback Event Polling

        private static Dictionary<double, List<System.Action>> _actionQueue =
            new Dictionary<double, List<System.Action>>();
        private const string CallBackName = "OnTimeCallBack";
        public static int PollInterval = 1;

        //[ExcelCommand(Name = CallBackName, IsExceptionSafe = false, MenuName = "Cubicle", MenuText = "Run queued actions")]
        public static void OnTimeCallBack()
        {
            // Select all the actions the should have been executed by now
            var now = (double)XlCall.Excel(XlCall.xlfNow);
            var todo = _actionQueue
                .Where(action => action.Key < now)
                .OrderBy(x => x.Key)
                .ToList();

            // If there are actions, execute them
            if (todo.Count() > 0)
            {
                foreach (var list in todo)
                {
                    foreach (var action in list.Value)
                        try
                        {
                            action();
                        } 
                        catch (Exception e)
                        {
                            _logger.ErrorException("Scheduled action caused an exception.", e);
                        }
                    _actionQueue.Remove(list.Key);
                }
            }

            // Set up next callback
            if (PollInterval > 0)
            {
                var nowNow = (double)XlCall.Excel(XlCall.xlfNow);
                var later = nowNow + PollInterval / (60.0 * 60.0 * 24.0);
                XlCall.Excel(XlCall.xlcOnTime, later, CallBackName);
            }
        }

        public static bool EnqueueAction(System.Action a, int secondsFromNow = 3)
        {
            // Hook up callback
            var now = (double)XlCall.Excel(XlCall.xlfNow);
            var scheduleTime = now + secondsFromNow / (60.0 * 60.0 * 24.0);
            if (!_actionQueue.ContainsKey(scheduleTime))
                _actionQueue[scheduleTime] = new List<System.Action>();
            _actionQueue[scheduleTime].Add(a);
            return true;
        }

        #endregion

        #region Functions: Misc

        /// <summary>
        /// Test addition
        /// </summary>
        /// <returns>An array</returns>
        [ExcelFunction(
            Name = "cTestAdd",
            Category = "Cubicle (Misc)",
            Description = "Test add",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static double TestAdd(double a, double b)
        {
            return a + b;
        }

        /// <summary>
        /// Combine individual cells into an array
        /// </summary>
        /// <returns>An array</returns>
        [ExcelFunction(
            Name = "cOffset",
            Category = "Cubicle (Misc)",
            Description = "Remove rows/columns from a 2D array",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object Offset(object[,] data, int rows, int cols)
        {
            var r = data.GetLength(0);
            var c = data.GetLength(1);
            var result = new object[r - rows, c - cols];
            for (int i = 0; i < r - rows; i++)
                for (int j = 0; j < c - cols; j++)
                    result[i, j] = data[i + rows, j + cols];
            return result;
        }

        /// <summary>
        /// Combine individual cells into an array
        /// </summary>
        /// <returns>An array</returns>
        [ExcelFunction(
            Name = "cArray",
            Category = "Cubicle (Misc)",
            Description = "Combine individual cells into an array",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object[] Array(object p1, object p2, object p3, object p4, object p5, object p6, object p7, object p8, object p9, object p10)
        {
            var result = new List<object>() { p1, p2, p3, p4, p5, p6, p7, p8, p9, p10 };
            return result.Where(x => !(x is ExcelMissing)).ToArray();
        }

        /// <summary>
        /// Convert .NET ticks to date time
        /// </summary>
        /// <param name="Ticks">Long ticks</param>
        /// <returns>String date time</returns>
        [ExcelFunction(
            Name = "cToDate",
            Category = "Cubicle (Misc)",
            Description = "Convert .NET ticks to date time",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object ToDate(long ticks)
        {
            return new DateTime(ticks);
        }

        /// <summary>
        /// Enumerates all names of an enum
        /// </summary>
        /// <param name="enumName">An Enum type name</param>
        /// <returns>A list of enum type names</returns>
        [ExcelFunction(
            Name = "cListEnumNames",
            Category = "Cubicle (Misc)",
            Description = "List the names in an Enum",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object ListEnumNames(string enumName)
        {
            return Enum.GetNames(Type.GetType(enumName)).ToRange(false);
        }

        /// <summary>
        /// Reverses an array of parameters
        /// </summary>
        /// <param name="data">An array of data items</param>
        /// <returns>The input data items, in reverse order.</returns>
        [ExcelFunction(
            Name = "cReverse",
            Category = "Cubicle (Misc)",
            Description = "Reverse an array of values",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object[] Reverse(object[] data)
        {
            return data.Reverse().ToArray();
        }

        public static string CallingCellReference() 
        { 
            ExcelReference reference = (ExcelReference)XlCall.Excel(XlCall.xlfCaller);
            string cellReference = (string)XlCall.Excel(
                XlCall.xlfAddress, 
                1+reference.RowFirst, 
                1+reference.ColumnFirst); 
            string sheetName = (string)XlCall.Excel(XlCall.xlSheetNm, reference); 
            cellReference = sheetName + cellReference; 
            return cellReference; 
        }

        public static string CallingSheetName()
        {
            ExcelReference reference = (ExcelReference)XlCall.Excel(XlCall.xlfCaller);
            string sheetName = (string)XlCall.Excel(XlCall.xlSheetNm, reference);
            return sheetName;
        }

        // This function fails if the book has not been saved yet. 
        public static string CallingFileName()
        {
            ExcelReference reference = (ExcelReference)XlCall.Excel(XlCall.xlfCaller);
            string sheetName = (string)XlCall.Excel(XlCall.xlSheetNm, reference);
            return Path.Combine(
                (string)XlCall.Excel(XlCall.xlfGetDocument, 2, sheetName),
                (string)XlCall.Excel(XlCall.xlfGetDocument, 88, sheetName));
        }

        [ExcelFunction(
            Name = "cAddInVersion",
            Category = "Cubicle (Misc)",
            Description = "Get the Cubicle Excel Add-In version",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object CubicleVersion()
        {
            return Assembly.GetExecutingAssembly().GetName().Version.ToString();
        }

        [ExcelFunction(
            Name = "cExcelVersion",
            Category = "Cubicle (Misc)",
            Description = "Get the Excel version",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static double ExcelVersion()
        {
            return ExcelDnaUtil.ExcelVersion;
        }

        /// <summary>
        /// Returns the Cubicle Add-In path
        /// </summary>
        /// <returns>The Cubicle Add-In path</returns>
        [ExcelFunction(
            Name = "cPath",
            Category = "Cubicle (Misc)",
            Description = "Retrieve the path of the Cubicle Add-In",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static string XllPath()
        {
            return Path.GetDirectoryName((string)XlCall.Excel(XlCall.xlGetName));
        }

        /// <summary>
        /// Allows users to synchronize the Excel dependency tree by creating a dependent on multiple nodes
        /// </summary>
        /// <returns>The first parameter</returns>
        [ExcelFunction(
            Name = "cSync",
            Category = "Cubicle (Misc)",
            Description = "Synchronize parameter references",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object Sync(
            object p1, object p2, object p3, object p4, object p5, object p6, object p7, object p8
            , object p9, object p10, object p11, object p12, object p13, object p14, object p15, object p16
            , object p17, object p18, object p19, object p20, object p21, object p22, object p23, object p24
            , object p25, object p26, object p27, object p28, object p29, object p30, object p31, object p32
            , object p33, object p34, object p35, object p36, object p37, object p38, object p39, object p40
            , object p41, object p42, object p43, object p44, object p45, object p46, object p47, object p48
            , object p49, object p50, object p51, object p52, object p53, object p54, object p55, object p56
            , object p57, object p58, object p59, object p60)
        {
            return p1;
        }

        /// <summary>
        /// Creates an array parameter from non-sequential cells
        /// </summary>
        /// <returns>An array object</returns>
        [ExcelFunction(
            Name = "cArray",
            Category = "Cubicle (Misc)",
            Description = "Combines individual cell references into an array.",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = false,
            IsVolatile = false)]
        public static object[] MakeArray(
            object p1, object p2, object p3, object p4, object p5, object p6, object p7, object p8
            , object p9, object p10, object p11, object p12, object p13, object p14, object p15, object p16
            , object p17, object p18, object p19, object p20, object p21, object p22, object p23, object p24
            , object p25, object p26, object p27, object p28, object p29, object p30, object p31, object p32
            , object p33, object p34, object p35, object p36, object p37, object p38, object p39, object p40
            , object p41, object p42, object p43, object p44, object p45, object p46, object p47, object p48
            , object p49, object p50, object p51, object p52, object p53, object p54, object p55, object p56
            , object p57, object p58, object p59, object p60)
        {
            var items = new List<object>();
            if (p1 == ExcelMissing.Value) return items.ToArray();
            items.Add(p1);
            if (p2 == ExcelMissing.Value) return items.ToArray();
            items.Add(p2);
            if (p3 == ExcelMissing.Value) return items.ToArray();
            items.Add(p3);
            if (p4 == ExcelMissing.Value) return items.ToArray();
            items.Add(p4);
            if (p5 == ExcelMissing.Value) return items.ToArray();
            items.Add(p5);
            if (p6 == ExcelMissing.Value) return items.ToArray();
            items.Add(p6);
            if (p7 == ExcelMissing.Value) return items.ToArray();
            items.Add(p7);
            if (p8 == ExcelMissing.Value) return items.ToArray();
            items.Add(p8);
            if (p9 == ExcelMissing.Value) return items.ToArray();
            items.Add(p9);
            if (p10 == ExcelMissing.Value) return items.ToArray();
            items.Add(p10);
            if (p11 == ExcelMissing.Value) return items.ToArray();
            items.Add(p11);
            if (p12 == ExcelMissing.Value) return items.ToArray();
            items.Add(p12);
            if (p13 == ExcelMissing.Value) return items.ToArray();
            items.Add(p13);
            if (p14 == ExcelMissing.Value) return items.ToArray();
            items.Add(p14);
            if (p15 == ExcelMissing.Value) return items.ToArray();
            items.Add(p15);
            if (p16 == ExcelMissing.Value) return items.ToArray();
            items.Add(p16);
            if (p17 == ExcelMissing.Value) return items.ToArray();
            items.Add(p17);
            if (p18 == ExcelMissing.Value) return items.ToArray();
            items.Add(p18);
            if (p19 == ExcelMissing.Value) return items.ToArray();
            items.Add(p19);
            if (p20 == ExcelMissing.Value) return items.ToArray();
            items.Add(p20);
            if (p21 == ExcelMissing.Value) return items.ToArray();
            items.Add(p21);
            if (p22 == ExcelMissing.Value) return items.ToArray();
            items.Add(p22);
            if (p23 == ExcelMissing.Value) return items.ToArray();
            items.Add(p23);
            if (p24 == ExcelMissing.Value) return items.ToArray();
            items.Add(p24);
            if (p25 == ExcelMissing.Value) return items.ToArray();
            items.Add(p25);
            if (p26 == ExcelMissing.Value) return items.ToArray();
            items.Add(p26);
            if (p27 == ExcelMissing.Value) return items.ToArray();
            items.Add(p27);
            if (p28 == ExcelMissing.Value) return items.ToArray();
            items.Add(p28);
            if (p29 == ExcelMissing.Value) return items.ToArray();
            items.Add(p29);
            if (p30 == ExcelMissing.Value) return items.ToArray();
            items.Add(p30);
            if (p31 == ExcelMissing.Value) return items.ToArray();
            items.Add(p31);
            if (p32 == ExcelMissing.Value) return items.ToArray();
            items.Add(p32);
            if (p33 == ExcelMissing.Value) return items.ToArray();
            items.Add(p33);
            if (p34 == ExcelMissing.Value) return items.ToArray();
            items.Add(p34);
            if (p35 == ExcelMissing.Value) return items.ToArray();
            items.Add(p35);
            if (p36 == ExcelMissing.Value) return items.ToArray();
            items.Add(p36);
            if (p37 == ExcelMissing.Value) return items.ToArray();
            items.Add(p37);
            if (p38 == ExcelMissing.Value) return items.ToArray();
            items.Add(p38);
            if (p39 == ExcelMissing.Value) return items.ToArray();
            items.Add(p39);
            if (p40 == ExcelMissing.Value) return items.ToArray();
            items.Add(p40);
            if (p41 == ExcelMissing.Value) return items.ToArray();
            items.Add(p41);
            if (p42 == ExcelMissing.Value) return items.ToArray();
            items.Add(p42);
            if (p43 == ExcelMissing.Value) return items.ToArray();
            items.Add(p43);
            if (p44 == ExcelMissing.Value) return items.ToArray();
            items.Add(p44);
            if (p45 == ExcelMissing.Value) return items.ToArray();
            items.Add(p45);
            if (p46 == ExcelMissing.Value) return items.ToArray();
            items.Add(p46);
            if (p47 == ExcelMissing.Value) return items.ToArray();
            items.Add(p47);
            if (p48 == ExcelMissing.Value) return items.ToArray();
            items.Add(p48);
            if (p49 == ExcelMissing.Value) return items.ToArray();
            items.Add(p49);
            if (p50 == ExcelMissing.Value) return items.ToArray();
            items.Add(p50);
            if (p51 == ExcelMissing.Value) return items.ToArray();
            items.Add(p51);
            if (p52 == ExcelMissing.Value) return items.ToArray();
            items.Add(p52);
            if (p53 == ExcelMissing.Value) return items.ToArray();
            items.Add(p53);
            if (p54 == ExcelMissing.Value) return items.ToArray();
            items.Add(p54);
            if (p55 == ExcelMissing.Value) return items.ToArray();
            items.Add(p55);
            if (p56 == ExcelMissing.Value) return items.ToArray();
            items.Add(p56);
            if (p57 == ExcelMissing.Value) return items.ToArray();
            items.Add(p57);
            if (p58 == ExcelMissing.Value) return items.ToArray();
            items.Add(p58);
            if (p59 == ExcelMissing.Value) return items.ToArray();
            items.Add(p59);
            if (p60 == ExcelMissing.Value) return items.ToArray();
            items.Add(p60);
            return items.ToArray();
        }

        /// <summary>
        /// Logical AND
        /// </summary>
        /// <returns>The binary AND result of two integers</returns>
        [ExcelFunction(
            Name = "cAnd",
            Category = "Cubicle (Misc)",
            Description = "Binary AND",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = true,
            IsVolatile = false)]
        public static int And(int a, int b)
        {
            return a & b;
        }

        /// <summary>
        /// Logical OR
        /// </summary>
        /// <returns>The binary OR result of two integers</returns>
        [ExcelFunction(
            Name = "cOr",
            Category = "Cubicle (Misc)",
            Description = "Binary OR",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = true,
            IsVolatile = false)]
        public static int Or(int a, int b)
        {
            return a | b;
        }

        /// <summary>
        /// Logical XOR
        /// </summary>
        /// <returns>The binary XOR result of two integers</returns>
        [ExcelFunction(
            Name = "cXor",
            Category = "Cubicle (Misc)",
            Description = "Binary XOR",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = true,
            IsVolatile = false)]
        public static int Xor(int a, int b)
        {
            return a ^ b;
        }

        /// <summary>
        /// Binary shift left
        /// </summary>
        /// <returns>Binary shift left</returns>
        [ExcelFunction(
            Name = "cLeft",
            Category = "Cubicle (Misc)",
            Description = "Binary shift left",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = true,
            IsVolatile = false)]
        public static int Left(int value, int bitCount)
        {
            return value << bitCount;
        }

        /// <summary>
        /// Binary shift right
        /// </summary>
        /// <returns>Binary shift right</returns>
        [ExcelFunction(
            Name = "cRight",
            Category = "Cubicle (Misc)",
            Description = "Binary shift right",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = true,
            IsVolatile = false)]
        public static int Right(int value, int bitCount)
        {
            return value >> bitCount;
        }

        /// <summary>
        /// Write an info message
        /// </summary>
        /// <returns>Write an info message</returns>
        [ExcelFunction(
            Name = "cEcho",
            Category = "Cubicle (Misc)",
            Description = "Echo an info message to the logs",
            IsExceptionSafe = false,
            IsHidden = false,
            IsMacroType = false,
            IsThreadSafe = true,
            IsVolatile = false)]
        public static string EchoMessage(string message)
        {
            _logger.Info(message);
            return message;
        }

        #endregion

    }
}
